<?php
/*
 * 调试查看工具
 */
function debug_info()
{
    global $db_configs;
    if (DEBUG == false)
        return false;

    $memory_get_usage = round(memory_get_usage(true) / 1048576, 2);
    $execute_time = round(microtime(true) - START_TIME, 3);

    $redis_logs = RedisFactory::getInstance()->getLog();
    $redis_connect_time = 0;
    foreach (RedisFactory::getInstances() as $redis_instance)
    {
        $redis_connect_time += $redis_instance->getConnTime();
    }

    $db_logs = array();
    $db_connect_time = 0;
    foreach (DBFactory::getInstances() as $db_link => $db_instance)
    {
        $db_logs[$db_link . '[' . $db_configs[$db_link]['dbname'] . ']'] = $db_instance->getLog();
        $db_connect_time += $db_instance->getConnTime();
    }
    
    $debug_html = <<<DEBUG_HTML
  <style>
    #debug_info_tool{
      border-top: 1px solid #999;
      border-left: 1px solid #999;
      border-right: 2px solid #999;
      border-bottom: 2px solid #999;
      width: 600px;
      font-size:12px;
      font-family: verdana fixedsys;
      padding: 2px;
      z-index: 1000;
      text-align: left;
      position: fixed;
      #position: absolute;
      _position: absolute;
      top: expression(offsetParent.scrollTop + 35);
      overflow: hidden;
    }
    #debug_info_tool li{
        list-style:decimal;
        background-image:none;
        padding: 3px 1em;
        border-bottom: 1px dotted black;
    }
    #debug_info_tool ol{
        background-color:#fff;
        max-height:400px;overflow:auto;
        _height:expression(this.scrollHeight > 400 ? "400px" : "auto");
        margin-left:0em; padding-left:0.2em; margin-bottom:0;
    }
  </style>
  <div id="debug_info_tool" style="top: 0px; left: 3px;">
    <div style="background-color:#39f; color:#fff; padding: 2px;">
      <div style="float: right; cursor: pointer; padding: 0 4px;" onmouseover="this.style.backgroundColor='#fff'; this.style.color='#39f';" onmouseout="this.style.backgroundColor='#39f'; this.style.color='#fff';" onclick="debug_hide('debug_info_tool');">Ⅹ</div>
      <div style="float: right; cursor: pointer; padding: 0 4px;" onmouseover="this.style.backgroundColor='#fff'; this.style.color='#39f';" onmouseout="this.style.backgroundColor='#39f'; this.style.color='#fff';" onclick="if(!debug_isShow('debug_info_body')){debug_show('debug_info_body');this.innerHTML='—';debug_id('debug_info_tool').style.width='600px';}else{debug_hide('debug_info_body');this.innerHTML='口';debug_id('debug_info_tool').style.width='300px';}">—</div>
      <div style="cursor: pointer;" id="debug_info_title"><b>调试工具栏(时间:{execute_time}秒, 内存:{memory_get_usage}MB)</b></div>
    </div>
    <div id="debug_info_body" style="display:none;">
        <div style="padding: 1px; border-bottom: 1px solid black;">
          <div style="cursor: pointer;background-color:#fff;" onclick="debug_hide('db_info');debug_hide('debug_info');debug_id('redis_info').style.display=(debug_isShow('redis_info')?'none':'block')"><b>REDIS查询次数:{redis_query_count}, 命中次数:{redis_succ_count}, MISS次数:{redis_fail_count}, 总连接时间:{redis_connect_time}秒, 总执行时间:{redis_total_time}秒</b></div>
          <div style="display: none;" id="redis_info">
            <ol>
              {redis_info}
            </ol>
          </div>
        </div>
        <div style="padding: 1px; border-bottom: 1px solid black;">
          <div style="cursor: pointer;background-color:#fff;" onclick="debug_hide('redis_info');debug_hide('debug_info');debug_id('db_info').style.display=(debug_isShow('db_info')?'none':'block')"><b>DB查询次数:{db_query_count}次, 总连接时间:{db_connect_time}, 总执行时间:{db_total_time}秒, 出错SQL数:{error_query_num}</b></div>
          <div style="display: none;" id="db_info">
            <ol>
              {db_info}
            </ol>
          </div>
        </div>
        <div style="padding: 1px;">
          <div style="cursor: pointer;background-color:#fff;" onclick="debug_hide('redis_info');debug_hide('db_info');debug_id('debug_info').style.display=(debug_isShow('debug_info')?'none':'block')"><b>调试信息栏{xhprof}</b></div>
          <div style="display: none;" id="debug_info">
            <ol>
              {debug_info}
            </ol>
          </div>
        </div>
     </div>
  </div>
  <script>
    debug_show('debug_info_body');
    function debug_id(id)
    {
        return document.getElementById(id);
    }
    function debugGetElementsByClassName(className, parentElement){
        var elems = (debug_id(parentElement)||document.body).getElementsByTagName("*");
        var result=[];
        for (i=0; j=elems[i]; i++){
            if ((" "+j.className+" ").indexOf(" "+className+" ")!=-1){
                result.push(j);
            }
        }
        return result;
    }
    function debug_show(ele)
    {
        if (typeof ele == 'string')
            ele = debug_id(ele);
        ele.style.display = 'block';
    }
    function debug_hide(ele)
    {
        if (typeof ele == 'string')
            ele = debug_id(ele);
        ele.style.display = 'none';
    }
    function debug_isShow(ele)
    {
        if (typeof ele == 'string')
            ele = debug_id(ele);
        return ele.style.display == 'block';
    }
    var DebugDrag = {
        obj : null,
        init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper)
        {
            o.onmousedown	= DebugDrag.start;
            o.hmode			= bSwapHorzRef ? false : true ;
            o.vmode			= bSwapVertRef ? false : true ;
            o.root = oRoot && oRoot != null ? oRoot : o ;
            if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = "0px";
            if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = "0px";
            if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = "0px";
            if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";
            o.minX	= typeof minX != 'undefined' ? minX : null;
            o.minY	= typeof minY != 'undefined' ? minY : null;
            o.maxX	= typeof maxX != 'undefined' ? maxX : null;
            o.maxY	= typeof maxY != 'undefined' ? maxY : null;
            o.xMapper = fXMapper ? fXMapper : null;
            o.yMapper = fYMapper ? fYMapper : null;
            o.root.onDragStart	= new Function();
            o.root.onDragEnd	= new Function();
            o.root.onDrag		= new Function();
        },
        start : function(e)
        {
            var o = DebugDrag.obj = this;
            o.style.cursor = 'move';
            e = DebugDrag.fixE(e);
            var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
            var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
            o.root.onDragStart(x, y);
            o.lastMouseX	= e.clientX;
            o.lastMouseY	= e.clientY;
            if (o.hmode) {
                if (o.minX != null)	o.minMouseX	= e.clientX - x + o.minX;
                if (o.maxX != null)	o.maxMouseX	= o.minMouseX + o.maxX - o.minX;
            } else {
                if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
                if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
            }
            if (o.vmode) {
                if (o.minY != null)	o.minMouseY	= e.clientY - y + o.minY;
                if (o.maxY != null)	o.maxMouseY	= o.minMouseY + o.maxY - o.minY;
            } else {
                if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
                if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
            }
            document.onmousemove	= DebugDrag.drag;
            document.onmouseup		= DebugDrag.end;
            return false;
        },
        drag : function(e)
        {
            e = DebugDrag.fixE(e);
            var o = DebugDrag.obj;
            var ey	= e.clientY;
            var ex	= e.clientX;
            var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
            var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
            var nx, ny;
            if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
            if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
            if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
            if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);
            nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
            ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
            if (o.xMapper)		nx = o.xMapper(y)
            else if (o.yMapper)	ny = o.yMapper(x)
            DebugDrag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
            DebugDrag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
            DebugDrag.obj.lastMouseX	= ex;
            DebugDrag.obj.lastMouseY	= ey;
            DebugDrag.obj.root.onDrag(nx, ny);
            return false;
        },
        end : function()
        {
            DebugDrag.obj.style.cursor = 'pointer';
            document.onmousemove = null;
            document.onmouseup   = null;
            DebugDrag.obj.root.onDragEnd(	parseInt(DebugDrag.obj.root.style[DebugDrag.obj.hmode ? "left" : "right"]),
                                        parseInt(DebugDrag.obj.root.style[DebugDrag.obj.vmode ? "top" : "bottom"]));
            DebugDrag.obj = null;
        },
        fixE : function(e)
        {
            if (typeof e == 'undefined') e = window.event;
            if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
            if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
            return e;
        }
    };
    DebugDrag.init(debug_id('debug_info_title'), debug_id('debug_info_tool'));

    function mousePosition(ev){
        if(ev.pageX || ev.pageY){
            return {x:ev.pageX, y:ev.pageY};
        }
        return {
            x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
            y:ev.clientY + document.body.scrollTop - document.body.clientTop
        };
    }
    function debugCreateToolTip(dom, ev) {
        if (!debug_id("debug_tip")) { // only one tip should be active at a time
            var tiptext = dom.getAttribute('tooltip');
            // create div for text
            var divtip = document.createElement("div");
            divtip.setAttribute('id',"debug_tip");
            document.body.appendChild(divtip);

            var elm_div = debug_id("debug_tip"); // lets grab the elements we just created

            // set styles on the tip box
            elm_div.style.border = "solid 1px black";
            elm_div.innerHTML =  tiptext;
            elm_div.style.position = "absolute";
            elm_div.style.background = "#ffffcc";
            elm_div.style.zIndex = "2000";
            elm_div.style.textAlign = "left";

            // position the top of the tool tip
            var mouse_pos = mousePosition(ev);
            elm_div.style.top = (mouse_pos.y + 5) + 'px';
            elm_div.style.left = (mouse_pos.x + 5) + 'px';
        }
    }
    function debugRemoveToolTip() {
        // remove the tool tip div and the image associated with it
        if (debug_id("debug_tip")) {
            var elm = debug_id("debug_tip");
            document.body.removeChild(elm);
        }
    }
    var tooltipDoms = debugGetElementsByClassName('tooltip', 'debug_info_tool');
    var n;
    for (n in tooltipDoms)
    {
        tooltipDoms[n].onmouseover = function(ev){
            ev = ev || window.event;
            debugCreateToolTip(this, ev);
        }
        tooltipDoms[n].onmouseout = function(){
            debugRemoveToolTip();
        }
    }

    // bind link tag
    function hanlder(event){
        var debug_event = event? event: window.event;
        var debug_obj = event.srcElement ? event.srcElement:event.target;

        if (debug_obj.tagName.toLowerCase() == 'a' && debug_obj.href.match(/^http:\/\/y\d?\.pps\.tv(\/.+)?/i)){
            var link = debug_obj.href;
            if (debug_event.preventDefault){
                debug_event.preventDefault();
            }else{
                window.event.returnValue = false;
            }

            if (link.indexOf('?') != -1){
                if (link.indexOf('#') != -1){
                    link = link.substring(0, link.indexOf('#')) + '&debug' + link.substring(link.indexOf('#'), link.length - 1);
                }else{
                    link += '&debug';
                }
            }else{
                if (link.indexOf('#') != -1){
                    link = link.substring(0, link.indexOf('#')) + '?debug' + link.substring(link.indexOf('#'), link.length - 1);
                }else{
                    link += '?debug';
                }
            }
            location.href = link;
        }
        else
        {
//            alert(debug_obj.tagName + ':' + debug_obj.href.substring(0, 4).toLowerCase());
        }
    }
    if(window.attachEvent){
        document.attachEvent("onclick", hanlder);
    }
    else{
        document.addEventListener("click", hanlder, true);
    }
  </script>
DEBUG_HTML;


    // redis info
    $rediss = array();
    $nodes_num = array();
    $group_time = array();
    foreach ($redis_logs as $redis_log)
    {
        if (isset($group_time[$redis_log['redis']]))
            $group_time[$redis_log['redis']] += $redis_log['time'];
        else
            $group_time[$redis_log['redis']] = $redis_log['time'];

        $key = md5($redis_log['func'] . join(',', $redis_log['params']));
        if (isset($rediss[$key]))
        {
            $rediss[$key]++;
        }
        else
        {
            $rediss[$key] = 1;
        }

        if (isset($nodes_num[$redis_log['redis']][$redis_log['group']][$redis_log['config']]))
            $nodes_num[$redis_log['redis']][$redis_log['group']][$redis_log['config']]++;
        else
            $nodes_num[$redis_log['redis']][$redis_log['group']][$redis_log['config']] = 1;
    }
    $redis_info = '';
    foreach ($nodes_num as $redis => $group_num)
    {
        $group_total_num = 0;
        foreach ($group_num as $group => $node_num)
        {
            $group_total_num += array_sum($node_num);
        }

        $redis_info .= '<li style="background-color:#eee;"><b>' . $redis . '</b>, 执行时间：' . round($group_time[$redis], 6) . '秒<br/>';
        foreach ($group_num as $group => $node_num)
        {
            $redis_info .= '&nbsp;&nbsp;' . $group . '<br/>';
            foreach ($node_num as $node => $num)
            {
                $redis_info .= '&nbsp;&nbsp;&nbsp;&nbsp;' . $node . '查询次数:' . $num . '次, 节点组内使用比例：' . round($num * 100 / $group_total_num, 2) . '%<br/>';
            }
        }
        $redis_info .= '</li>';
    }

    $redis_succ_count = $redis_fail_count = $redis_total_time = 0;
    $all_redis_keys = array();
    foreach ($redis_logs as $redis_log)
    {
        $redis_md5_key = md5($redis_log['func'] . join(',', $redis_log['params']));
        $query_count = $rediss[$redis_md5_key];
        if ($redis_log['ret'])
        {
            $redis_succ_count++;
            if ($query_count > 1)
                $color = '#00f';
            else
                $color = '#000';
        }
        else
        {
            $redis_fail_count++;
            $color = '#f00';
        }

        $redis_total_time += $redis_log['time'];

        $trace_str = '';
        foreach ($redis_log['trace'] as $n => $trace)
        {
            $trace_str .= ($n + 1) . '.文件:' . str_replace(array('/home/httpd/t', '/home/httpd/y'), '', $trace['file']) . ', 行数:' . $trace['line'] . ', 方法:' . $trace['func'] . "()<br/>";
        }
        if (!in_array($redis_md5_key, $all_redis_keys))
        {
            $redis_info .= '<li style="color:' . $color . '" class="tooltip" tooltip="' . $trace_str . '">' . $redis_log['redis'] . '->' . $redis_log['group'] . '->' . $redis_log['config'] . '->' . $redis_log['func'] . '(' . join(', ', $redis_log['params']) . ')<br/>执行时间:' . round($redis_log['time'], 6) . '秒, 查询次数:' . $query_count . '</li>';
            $all_redis_keys[] = $redis_md5_key;
        }
    }


    // db info
    $sqls = array();
    $query_num = $error_query_num = 0;
    foreach ($db_logs as $_db_log)
    {
        foreach ($_db_log as $db_log)
        {
            $key = md5($db_log['sql'] . serialize($db_log['params']));
            if (isset($sqls[$key]))
            {
                $sqls[$key]++;
            }
            else
            {
                $sqls[$key] = 1;
            }

            if ($db_log['error'])
                $error_query_num++;
            $query_num++;
        }
    }
    $db_info = '';

    $db_total_time = 0;
    $all_db_keys = array();
    foreach ($db_logs as $db_link => $_db_log)
    {
        foreach ($_db_log as $db_log)
        {
            $db_md5_key = md5($db_log['sql'] . serialize($db_log['params']));
            $query_count = $sqls[$db_md5_key];
            $color = $db_log['time'] > 0.01 ? '#f00' : ($query_count > 1 ? '#00f' : '#000');
            $params = array();
            foreach ($db_log['params'] as $k => $v)
            {
                $params[] = $k . ' => ' . $v;
            }

            $trace_str = '';
            foreach ($db_log['trace'] as $n => $trace)
            {
                $trace_str .= ($n + 1) . '.文件:' . str_replace(array('/home/httpd/t', '/home/httpd/y'), '', $trace['file']) . ', 行数:' . $trace['line'] . ', 方法:' . $trace['func'] . "()<br/>";
            }

            $db_total_time += $db_log['time'];
            if (! in_array($db_md5_key, $all_db_keys))
            {
                $error_str = $db_log['error'] ? '<br/><span style="color:#f60;"><b>Error</b>: ' . substr($db_log['error'], 0, strrpos($db_log['error'], '(')) . '</span>' : '';

                $db_info .= '<li style="color:' . $color . '" class="tooltip" tooltip="' . $trace_str . '">执行时间:' . round($db_log['time'], 3) . '秒,查询次数:' . $query_count . '<br/><b>' . $db_link . '->SQL</b>: <pre>' . str_replace("\t", "&nbsp;&nbsp;", $db_log['sql']) . '</pre>' . ($params ? '<br/><b>Params</b>: [ ' . join(', ', $params) . ' ]' : '') . $error_str . '</li>';
                $all_db_keys[] = $db_md5_key;
            }
        }
    }

    // debug info
    $debug_info = '';
    $time_debugs = array();
    foreach (time_debug() as $time_debug)
    {
        $time_debugs[$time_debug['name']] = $time_debug;
    }

    foreach (d('get_debug_data') as $m => $_debug_info)
    {
        $trace_str = '';
        $backtrace = $_debug_info['backtrace'];
        $pre_backtrace = array_shift($backtrace);
        foreach ($backtrace as $n => $trace)
        {
            $trace_str .= ($n + 1) . '.文件:' . str_replace(array('/home/httpd/t', '/home/httpd/y'), '', $trace['file']) . ', 行数:' . $trace['line'] . ', 方法:' . $trace['function'] . "()<br/>";
        }
        $time_debug_str = '';
        if (isset($time_debugs['debug_time_' . ($m + 1)]))
            $time_debug_str = ', 偏移时间:' . round($time_debugs['debug_time_' . ($m + 1)]['offset_time'], 3) . 's, 执行时间:' . round($time_debugs['debug_time_' . ($m + 1)]['exec_time'], 3) . 's';

        $debug_info .= '<li class="tooltip" tooltip="' . $trace_str . '">文件:' . str_replace(array('/home/httpd/t', '/home/httpd/y'), '', $pre_backtrace['file']) . ', 行数:' . $pre_backtrace['line'] . $time_debug_str . ', 信息:<br/>';
        $debug_info .= '<pre>' . var_export($_debug_info['info'], true) . '</pre>';
        $debug_info .= '</li>';
    }

    if (defined('XHPROF_VIEW_URL'))
        $xhprof = '(<a href="' . XHPROF_VIEW_URL . '" target="_blank">XHPROF</a>)';
    else
        $xhprof = '';

    // replace data
    $debug_html = str_replace(
        array('{execute_time}', '{memory_get_usage}', '{xhprof}', '{redis_info}', '{redis_connect_time}', '{redis_succ_count}', '{redis_fail_count}', '{redis_query_count}', '{redis_total_time}', '{db_info}', '{db_connect_time}', '{db_query_count}', '{db_total_time}', '{error_query_num}', '{debug_info}'),
        array($execute_time, $memory_get_usage, $xhprof, $redis_info, $redis_connect_time, $redis_succ_count, $redis_fail_count, count($redis_logs), round($redis_total_time, 3), $db_info, $db_connect_time, $query_num, round($db_total_time, 3), $error_query_num, $debug_info),
        $debug_html);

    echo $debug_html;
}

/*
 * facebook 查看程序运行瓶颈工具
 */
function xhprof_save()
{
    if (! DEBUG || !function_exists('xhprof_disable'))
    {
        return false;
    }

    $xhprof_data = xhprof_disable();

    include ROOT_PATH . "xhprof_lib/utils/xhprof_lib.php";
    include ROOT_PATH . "xhprof_lib/utils/xhprof_runs.php";

    $xhprof_runs = new XHProfRuns_Default();
    $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
    define('XHPROF_VIEW_URL', 'http://'.$_SERVER["HTTP_HOST"] . '/xhprof_html/index.php?run='.$run_id.'&source=xhprof_foo');
}


?>